/**
 * umcore.slim.js
 * # 概述
 * UMCore 库，用于完成以下功能：
 * 1. 全局的消息分发
 * 2. 动态加载html, js, css
 * 3. 数据缓冲
 * 4. 与umnode时行接口对接
 *
 * 依赖:
 * 1. jquery3.3+
 *
 * # 用法
 * 使用<script src="umcore.js"></script>引用到当前页面，就会在当前页面获取一个全局的`$UMCore`对象。
 * 如果变量名`$UMCore`与当前环境有冲突的话，可以用下面的方法来重命名此对象：
 *
 *        MyUMCore = $UMCore.noConflict()
 *
 * 在全局对象生成后，需要在页面的ready事件，或onload事件中，对UMCore进行初始化：
 *
 *      $UMCore.init().then(function(){ console.log('umcore is inited!') })
 *
 * 完成初始化后，就可以指定界面模块，发送事件，数据处理，调用接口等操作了。
 *
 * 在Vue工程中使用：
 *
 *
 *
 * # 常用接口
 *
 * 1. $UMCore.msg(object): 发送一条消息，所有此消息的侦听者都会收到消息通知。
 *      例如：
 *
 *          $UMCore.msg({cmd:"user", op:"add", info:"add a user", data:{} })
 *
 *      那么，所有侦听此事件（cmd="user") 的对象，都会回调它们的“add"方法，如果存在的话。
 *
 * 2. $UMCore.on(event, listener): 将一个对象(listener) 注册成为事件event的侦听者。event可以是string或Array
 * 3. $UMCore.addui(name, provider): 将一个界面提供者加入UMCore。界面提供者用于在动态加载界面时生成界面
 * 4. $UMCore.ui(selector, ui_name): 将一个界面动态加载到selector的位置。selector类似于jquery的$()方法参数。
 * 5. $UMCore.load(url, selector?, type?, data?): 动态加载一个html,css或js到指定selector中，selector可选。
 * 6. $UMCore.cache(op, data): 将data存入缓存中, 或从缓存中获取数据，op='get' 或 op='set' 来控制
 * 7. $UMCore.api(api, data): 调用api, 然后链接.then()进行处理接口数据
 *
 * @author yxb
 * @copyright intwork.com 2018
 */
import $ from 'jquery'
import axios from 'axios'
import {
  Message
} from 'element-ui'
import store from '../store'

import {
  getGlobalSetting,
  setGlobalSetting,
  removeGlobalSetting
} from './utils'
const md5 = require('md5')

export default function UmCore (global) {
  // console.log('umcore', $, axios, {getGlobalSetting, setGlobalSetting, removeGlobalSetting})
  // jQuery is needed
  var needRelogin = false
  var _$UMCore = global.$UMCore
  var theCore = null

  // umcore的内部数据
  var _data = {

    // 事件监听器列表，通过名字关联. 比如临听user事件，可以得到用户相关的消息
    listeners: {
      umnode: [{
        process: function (ummsg, umcore) {
          // websockt is OK, init exlist.
          if (ummsg.code === 0) {
            console.log('umnode is ready, start init umcore.js library, device id is:' + deviceId())
            _data.ws_name = ummsg.name
            setGlobalSetting('ws_name', _data.ws_name, true)
            setGlobalSetting('deviceId', deviceId(), true)
            // compile('ws_name', _data.ws_name)
            return true
          }
        },
        on_receive: function (ummsg, umcore) {
          if (ummsg.code < 0) {
            console.warning('ummsg.code<0, clear token')
            // compile('token', null)
          }
        }
      }]
    },

    // 服务或UI提供者，按能提供的消息列在此处
    providers: {

    },

    // websocket的连接
    websocket: undefined,

    // error 错误信息
    error: undefined,

    // sessionid 事务id
    sessionid: undefined,

    // token for jwt
    jwtToken: '',

    // name of websocket
    ws_name: '',

    // ping/pong count
    ping_count: 0,

    // deviceid
    deviceid: ''
  }

  function has (obj, prop, type) {
    if (obj) {
      if (type) {
        switch (type) {
          case 'object':
            return obj.hasOwnProperty(prop) && typeof (obj[prop]) === 'object'
          case 'string':
            return obj.hasOwnProperty(prop) && typeof (obj[prop]) === 'string'
          case 'function':
            return obj.hasOwnProperty(prop) && typeof (obj[prop]) === 'function'
          default:
            return false
        }
      }
      return obj.hasOwnProperty(prop)
    }
    return false
  }

  // 处理监听器事件, 监听者可以自己实现process方法，
  // 也可以直接监听cmd, cmd.op两种事件
  function process (listener, ummsg, umcore) {
    if (has(listener, 'process', 'function')) {
      return listener.process(ummsg, umcore)
    } else {
      let key = ummsg.cmd + '.' + ummsg.op // "cmd.op"
      if (has(listener, ummsg.op, 'function')) {
        return listener[ummsg.op](ummsg, umcore)
      }
      if (has(listener, key, 'function')) {
        return listener[key](ummsg, umcore)
      }
    }
  }

  // 分发信息
  function dispatchUmmsg (ummsg, umcore) {
    if (typeof (ummsg) === 'string') {
      ummsg = JSON.parse(ummsg)
    }
    console.log(ummsg)
    if (has(ummsg, 'cmd', 'string')) {
      var isprocessed = false
      if (has(_data.listeners, ummsg.cmd, 'object')) {
        var listener = _data.listeners[ummsg.cmd]
        if (Array.isArray(listener)) {
          listener.forEach(function (value) {
            var ret = process(value, ummsg, umcore)
            if (!isprocessed) isprocessed = ret
          })
        } else {
          isprocessed = process(listener, ummsg, umcore)
        }
      }
      if (!isprocessed) {
        // 不支持的消息，自动获取
        console.warn('no local processer for ummsg:', ummsg)
      }
    } else {
      console.warn('error ummsg format:', ummsg)
    }
  }

  // 用于处理websocket网络连接，主要是一些异步回调事件
  function processWebsocket (umcore) {
    return new Promise(function (resolve, reject) {
      console.log('connecting to ws server...')
      var wsproto = (location.protocol === 'https:') ? 'wss:' : 'ws:'
      var host = wsproto + '//' + window.location.host + '/websocket'
      if (umcore.config && umcore.config.wshost) {
        host = umcore.config.wshost
      }
      host += `?f=${encodeURIComponent(location.href)}&a=${encodeURIComponent(navigator.userAgent)}&t=umrtc`
      var conn = new WebSocket(host)
      _data.websocket = conn
      conn.onopen = function (e) {
        console.log('websocket connect success!')
        setGlobalSetting('network', true)
        $('.error').removeClass('errorShow')
        if (needRelogin && getGlobalSetting('userinfo')) {
          if (window.location.href.indexOf('login') === -1) {
            reloadLogin()
            needRelogin = false
          }
        } else {
          theCore.token(getGlobalSetting('token'))
        }
        resolve(umcore)
      }
      conn.onmessage = function (e) {
        // process ping/pong response
        if (e.data === 'pong') {
          console.log('pong')
          _data.ping_count = 0
          return
        }

        var ummsg = JSON.parse(e.data)
        if (typeof (ummsg) === 'string') {
          let ummsgobj = JSON.parse(ummsg)
          if (ummsgobj) {
            ummsg = ummsgobj
          }
        }
        dispatchUmmsg(ummsg, umcore)
      }
      conn.onerror = function (error) {
        _data.websocket = null
        console.log('websocket is error:', error)
        setGlobalSetting('network', false)
        $('.error').addClass('errorShow')
        reject(error)
      }
      conn.onclose = function (e) {
        console.log('websocket is closed:', e, ', try connect after 3 secends...')
        needRelogin = true
        // curDate = new Date(),
        // alert('websocket链接断的时间:'+curDate.getFullYear()+'-'+(curDate.getMonth()+1)+'-'+curDate.getDate()+" "+(curDate.getHours()+1)+":"+(curDate.getMinutes()+1)+":"+(curDate.getSeconds()+1))
        setTimeout(processWebsocket, 3000, umcore)
      }
    })
  };

  function reloadLogin () {
    if (!getGlobalSetting('userinfo') || !getGlobalSetting('userinfo').umid) {
      console.warn('reloadLogin with no userinfo, abort.')
      return
    }
    var umid = getGlobalSetting('userinfo').umid
    let curDate = new Date()
    let year = curDate.getFullYear().toString()
    let month = curDate.getMonth() + 1
    let date = curDate.getDate()
    let fulldate = ''
    if (month < 10) {
      month = '0' + (curDate.getMonth() + 1).toString()
    } else {
      month = (curDate.getMonth() + 1).toString()
    }
    if (date < 10) {
      date = '0' + curDate.getDate().toString()
    } else {
      date = curDate.getDate().toString()
    }
    fulldate = year + month + date
    let password = md5(umid + 'UMGrit' + fulldate)
    // Message({
    //   message: '设备id=' + deviceId() + ',websocket_name=' + _data.ws_name,
    //   type: 'error',
    //   duration: 500
    // })
    var data = JSON.stringify({
      functionname: 'loginfortype',
      username: umid,
      password: password,
      logintype: 7
    })

    $.ajax({
      type: 'post',
      url: '/route.lua',
      data: JSON.stringify({
        data: data,
        websocket_name: _data.ws_name,
        deviceId: deviceId()
      }),
      contentType: 'application/json;charset=utf-8',
      dataType: 'json',
      success: function (response) {
        if (response.code === 0) {
          console.log('重新登录')
          setGlobalSetting('logoutStatus', false)
          removeGlobalSetting('umwxloginid')
          theCore.token(response.token)
          theCore.msg({
            cmd: 'reload',
            op: 'refresh'
          })
        } else if (response.code === 4) {
          Message({
            message: response.msg,
            type: 'error',
            onClose: () => {
              store.dispatch('LOGOUT')
            }
          })
        }
      },
      complete: function (jqxhr, status) {
        console.log('websocket connect done. --- ', status)
        if (status === 'error' || status === 'timeout') {
          // 出错了，稍后重试
          setTimeout(reloadLogin, 3000)
        }
      }
    })
  }
  /**
   * 发送心跳到服务器。如果3次心跳后还没有收到回应，则报错重连。
   */
  function ping () {
    if (_data.websocket && _data.websocket.readyState === WebSocket.OPEN) {
      console.log('ping')
      _data.websocket.send('ping')
      _data.ping_count++
      if (_data.ping_count >= 3) {
        _data.websocket.close()
        _data.ping_count = 0
      }
    }
  }

  /**
   * 调用umnode的web接口。接口默认是post方式调用的。
   *
   * @param {string} api 接口名
   * @param {Object} msg 接口需要的数据
   * @param {string} method 接口需要的发送方式。默认是post
   * @param {bool} onlydata 是否只返回data, 默认是false
   * @param {object} axios Axios的实例
   * @returns {Promise} 接口返回的数据，状态等
   */
  function callWebapi (api, msg, method, onlydata, axiosobj) {
    if (msg === undefined) msg = {}
    if (!axiosobj) axiosobj = axios

    // 自动补全带域名的地址
    if (theCore && theCore.config && theCore.config.host) {
      api = theCore.config.host + api
    }

    msg.sessionid = getSessionID()
    if (!msg.websocket_name && msg.websocket_name !== '') {
      msg.websocket_name = _data.ws_name
    }
    let send = null
    if (method === 'get') {
      send = axiosobj.get(api, {
        headers: {
          'Authorization': _data.jwtToken
        },
        params: msg
      })
    } else {
      send = axiosobj.post(api, msg, {
        headers: {
          'Authorization': _data.jwtToken
        }
      })
    }
    if (send == null) {
      return false
    }
    return send.then(function (result) {
      console.log('[debug]', result.data)
      if (result.data.code !== undefined && result.data.code !== 0) {
        _data.error = '接口调用失败:{' + api + '}, 返回的结果中没有code或code不为0'
      }
      if (onlydata) {
        return result.data
      } else {
        return result
      }
    })
      .catch(function (err) {
        console.log(err)
        _data.error = '发生异常:' + err
        return err
      })
  }

  /**
   * 加载一个本地已存在的扩展模块（DApp)
   *
   * @param {string} name 加载的模块名
   * @returns {Promise} 加载的模块名
   */
  function loadExt (name) {
    return loadContent('/build-js/main.js').then(function () {
      return name
    })
  }

  /**
   * 加载一个html或css或js. 如果type指定了类型，则以type指定的为准，如果type为空，则以路径文件的扩展名为准。
   * 异步加载。
   * 加载完后会替换selector的innerHtml
   *
   * @param {string} url 加载项的路径。如果type未指定，将会以url的扩展名为准
   * @param {string} selector 选择器，指定加载的内容显示在哪里。对css无效。
   * @param {string} type 类型，可选：css, js, html
   * @param {Object} data 如果指定data，且加载的是html的话，则按照Post的方法把data提交到url.
   * @returns {Promise} 加载的结果
   */
  function loadContent (url, selector, type, data) {
    if (selector === undefined) selector = ''
    if (type === undefined) type = ''
    if (data === undefined) data = ''

    return new Promise(function (resolve, reject) {
      if (type === '') {
        type = url.split('.').pop()
      }
      if (type === 'css') {
        $('head').append($('<link rel="stylesheet" type="text/css" />')
          .attr('href', url))
        console.log('load css', url)
        resolve({
          url: url,
          data: '',
          status: '',
          jqxhr: ''
        })
      } else if (type === 'js') {
        $.getScript(url, function (data, textStatus, jqxhr) {
          console.log('load js', url, textStatus)
          resolve({
            url: url,
            data: data,
            status: textStatus,
            jqxhr: jqxhr
          })
        })
      } else {
        // window["webpackJsonp"] = false
        $(selector).load(url, data, function (data, textStatus, jqxhr) {
          console.log('load', url, textStatus)
          resolve({
            url: url,
            data: data,
            status: textStatus,
            jqxhr: jqxhr
          })
        })
      }
    })
  }

  /**
   * 添加事件侦听器. 事件是跟据消息字段`cmd`进行分组的。
   * 支持 addListener(['ke1','key2'], listener) 这种方式
   * 和   addListener('key1', listener) 这种方式
   * @param {string|Array} name 事件名
   * @param {Object} listener 侦听者
   */
  function addListener (name, listener) {
    var _event = _data.listeners
    if (Array.isArray(name)) {
      name.forEach(function (value, index) {
        addListener(value, listener)
      })
    } else {
      if (!has(_event, name, 'object')) {
        _event[name] = [listener]
        return
      }

      // is exists
      var index = 0
      var found = false
      for (index = 0; index < _event[name].length; index++) {
        var value = _event[name][index]
        if (value === listener || (listener.__event_name && listener.__event_name === value.__event_name)) {
          found = true
          break
        }
      }
      if (found) {
        _event[name][index] = listener
      } else {
        _event[name].push(listener)
      }
    }
  }

  /**
   * 移除事件侦听器. 事件是跟据消息字段`cmd`进行分组的。
   * 支持 delListener(['ke1','key2'], listener) 这种方式
   * 和   delListener('key1', listener) 这种方式
   * @param {string|Array} name 事件名
   * @param {Object} listener 侦听者
   */
  function delListener (name, listener) {
    var _event = _data.listeners
    if (Array.isArray(name)) {
      name.forEach(function (value, index) {
        delListener(value, listener)
      })
    } else {
      if (!has(_event, name, 'object')) {
        return
      }
      var index = _event[name].indexOf(listener)
      if (index > -1) {
        _event[name].splice(index, 1)
      }
    }
  }

  /**
   * 添加UI提供者
   * provider 必须包含以下接口：
   * 1. showin(selector)
   * 2. showintoolbar(selector)
   *
   * @param {string|Array} name 界面名
   * @param {Object} provider 界面提供者
   */
  function addProvider (name, provider) {
    var _event = _data.providers
    if (Array.isArray(name)) {
      name.forEach(function (value, index) {
        addListener(value, provider)
      })
    } else {
      if (!has(_event, name, 'object')) {
        _event[name] = [provider]
        return
      }
      var found = _event[name].find(function (value) {
        return value === provider
      })
      if (!found) {
        _event[name].push(provider)
      }
    }
  }

  /**
   * 将指定的界面展示在selector的位置处
   *
   * @param {string} selector 选择器，类似jquery的$()方法参数。
   * @param {string} providerName 界面名，通过addui注册。
   * @param {number} providerIndex 如果界面名下有多个提供者，指定其序号。
   */
  async function showview (selector, providerName, providerIndex) {
    if (providerIndex === undefined) providerIndex = 0
    var p = _data.providers
    var provider
    if (has(p, providerName, '')) {
      if (Array.isArray(p[providerName])) {
        provider = p[providerName][providerIndex]
      } else {
        provider = p[providerName]
      }
      if (has(provider, 'showin', 'function')) {
        provider.showin(selector)
      } else if (typeof (provider) === 'function') {
        let result = await provider(selector)
        return result
      }
    }
  }

  /**
   * 将指定的界面展示在selector的位置处的【工具栏】上
   *
   * @param {string} selector 选择器，类似jquery的$()方法参数。
   * @param {string} providerName 界面名，通过addui注册。
   * @param {number} providerIndex 如果界面名下有多个提供者，指定其序号。
   */
  function showviewintoolbar (selector, providerName, providerIndex) {
    if (providerIndex === undefined) providerIndex = 0
    var p = _data.providers
    var provider
    if (has(p, providerName, 'object')) {
      if (Array.isArray(p[providerName])) {
        provider = p[providerName][providerIndex]
      } else {
        provider = p[providerName]
      }
      if (has(provider, 'showin')) {
        provider.showintoolbar(selector)
      }
    }
  }

  /**
   * 发出广播消息
   * @param {Object} msg 消息体，一般由以下项目组成：{cmd:"", op:"", info:"", data:{}}
   *                     其中cmd是消息指令，最大的分类。其次是op操作，指定是大类下的什么动作。info用于对指令作出说明，data是指令需要的数据。
   */
  function pushMsg (msg) {
    let pushapi = '/index.lua?a=msgloop'
    return callWebapi(pushapi, {
      msg: JSON.stringify(msg),
      websocket_name: msg.websocket_name
    }, 'post', true)
  }

  /**
   * 保存或获取全局缓冲数据
   * @param {string} op 取值 'get' or 'set'
   * @param {Object|Array|string} cache 在'set'时， 以 {key: value, key2: value2} 方式指定的数据。如果key或key1已存在，将会被新的值替换
   *                             在‘get'时， 以[key, key1] 或 ’key' 方式指定数据的键，从而得到它的值。
   */
  function cache (op, cache) {
    return callWebapi('/?a=cache&t=' + Math.random(), {
      op: op,
      cache: JSON.stringify(cache)
    })
  }

  /**
   *  初始化所有, 加载所有本地扩展
   */
  function initall (conf) {
    theCore.config = conf
    return processWebsocket(theCore) // 打开websocket网络连接
      // .then(function () {
      //   console.log('重新加载调试13')

      //   // return load_local_ext();
      //   return loadExt()
      // })
      .then(function () {
        return theCore
      })
  }

  /**
   * 设置Session id, 用于唯一标识当前会话
   * @param {string} sessionid
   */
  function setSessionID (sessionid) {
    _data.sessionid = sessionid
    createCookie('session', sessionid)
  }

  /**
   * 得到当前会话的Session id
   */
  function getSessionID () {
    _data.sessionid = readCookie('session')
    // if (_data.sessionid==undefined || _data.sessionid=="") {
    //     _data.sessionid = createCookie("session", "88888888");
    // }
    return _data.sessionid
  }

  /**
   * 得到或设置当前会话id
   * @param {string} session
   */
  function sessionID (session) {
    if (session !== undefined) {
      setSessionID(session)
    } else {
      return getSessionID()
    }
  }

  function deviceId () {
    if (_data.deviceid) {
      return _data.deviceid
    }

    let devid = localStorage.getItem('devid')
    if (!devid) {
      devid = '_' + Math.random().toString(36).substr(2, 9)
      localStorage.setItem('devid', devid)
    }
    _data.deviceid = devid
    return devid
  }

  function jwtToken (token) {
    if (token) {
      setGlobalSetting('token', token)
      _data.jwtToken = token
      // regist websocket
      _data.websocket.send(JSON.stringify({
        devid: deviceId(),
        token: token
      }))
    } else {
      if (!_data.jwtToken) {
        _data.jwtToken = getGlobalSetting('token')
      }
      return _data.jwtToken
    }
  }

  /**
   * 创建cookie
   * @param {string} name
   * @param {*} value
   * @param {date} days
   */
  function createCookie (name, value, days) {
    var expires

    if (days) {
      var date = new Date()
      date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000))
      expires = '; expires=' + date.toGMTString()
    } else {
      expires = ''
    }
    document.cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value) + expires + '; path=/'
  }

  /**
   * 得到cookie的值
   * @param {string} name
   */
  function readCookie (name) {
    var nameEQ = encodeURIComponent(name) + '='
    var ca = document.cookie.split(';')
    for (var i = 0; i < ca.length; i++) {
      var c = ca[i]
      while (c.charAt(0) === ' ') {
        c = c.substring(1, c.length)
      }
      if (c.indexOf(nameEQ) === 0) {
        return decodeURIComponent(c.substring(nameEQ.length, c.length))
      }
    }
    return null
  }

  function compile (name, code) {
    // if(isWeChatApplet() == "yes") {
    // if (code === 'nil') {
    //   localStorage.removeItem(name)
    // }
    cache2(name, code)
    // }
    // else {
    //     if(code == "nil") {
    //         localStorage.removeItem(name)
    //     }else if(code!=undefined){
    //         var c=String.fromCharCode(code.charCodeAt(0)+code.length);
    //         for(var i=1;i<code.length;i++){
    //             c+=String.fromCharCode(code.charCodeAt(i)+code.charCodeAt(i-1));
    //         }
    //         localStorage.setItem(name,escape(c))
    //     }
    //     else{
    //         return
    //     }
    // }
  }

  function uncompile (name) {
    // if(name == "token") {
    //     alert("isWeChatApplet " + isWeChatApplet())
    // }

    // if(isWeChatApplet() == "yes") {
    // alert("uncompile yes " + name)
    return cache2(name)
    // }
    // else {
    //     code=unescape(localStorage.getItem(name));
    //     if(code==='null'){
    //         return null;
    //     }else{
    //         var c=String.fromCharCode(code.charCodeAt(0)-code.length);
    //         for(var i=1;i<code.length;i++){
    //         c+=String.fromCharCode(code.charCodeAt(i)-c.charCodeAt(i-1));
    //         }
    //         return c;
    //     }
    // }
  }

  function getuseruuid () {
    var useruuid = unescape(getGlobalSetting('useruuid'))
    if (useruuid === 'null') {
      useruuid = guid()
      // alert("create - " + useruuid);
      setGlobalSetting('useruuid', escape(useruuid))
    }
    return useruuid
  }

  function setuseruuid (useruuid) {
    setGlobalSetting('useruuid', escape(useruuid))
  }

  function cachebyumnode (key, value) {
    var ret = cache2(key, value)
    // console.log("cachebyumnode = " + ret);
    return ret
  }

  function cache2 (name, cache) {
    var userid = getuseruuid()
    // console.log("userid = " + userid);
    // alert("get - " + userid);
    var data = {}
    var ret = getItemInUmcode(userid)
    if (ret === 'error' || !ret) {
      return ''
    }
    if (ret !== '') {
      data = JSON.parse(ret)
    }
    var vdata
    if (name === 'nil') {
      vdata = 'nil'
    } else if (cache === undefined) {
      if (data[name] === undefined) {
        return null
      }
      return data[name]
    } else if (cache === 'nil') {
      data[name] = ''
      vdata = JSON.stringify(data)
    } else {
      data[name] = cache
      vdata = JSON.stringify(data)
    }
    setItemInUmnode(userid, vdata)
  }

  function getItemInUmcode (node) {
    var data = getGlobalSetting(node)
    // $.ajax({
    //   type: 'post',
    //   url: '/?a=cache&t=' + Math.random(),
    //   data: {
    //     key: node
    //   },
    //   async: false,
    //   success: function (response) {
    //     // console.log('res', response)
    //     if (response.code === 'ok') {
    //       if (response.data !== undefined) {
    //         data = response.data
    //         // console.log(data);
    //       }
    //     }
    //   },
    //   error: function (XMLHttpRequest, textStatus, errorThrown) {
    //     // alert(textStatus);
    //     data = 'error'
    //   }
    // })
    return data
  }

  function setItemInUmnode (node, data) {
    setGlobalSetting(node, data)
    // $.ajax({
    //   type: 'post',
    //   url: '/?a=cache&t=' + Math.random(),
    //   data: {
    //     key: node,
    //     value: data
    //   },
    //   async: false,
    //   success: function (response) {
    //     // console.log('res', response)
    //   },
    //   error: function (XMLHttpRequest, textStatus, errorThrown) {
    //     // alert(textStatus);
    //   }
    // })
  }

  function guid () {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
      let r = Math.random() * 16 | 0
      let v = c === 'x' ? r : (r & 0x3 | 0x8)
      return v.toString(16)
    })
  }

  function isWeChatApplet () {
    var ua = window.navigator.userAgent.toLowerCase()
    var status
    // console.log("ua = " + ua.indexOf('micromessenger'));
    if (ua.indexOf('micromessenger') === -1) {
      // alert("no wx");
      return 'not wx'
    } else {
      status = 'no'
      window.wx.miniProgram.getEnv(function (res) {
        // console.log(res);

        if (res.miniprogram) {
          // 小程序环境下逻辑
          // console.log("小程序环境下")
          status = 'yes'
        } else {
          // 非小程序环境下逻辑
          // console.log("非小程序环境下")
          // alert(res);
          status = 'no'
        }
      })
      return status
    }
  }

  function pushUrl (url) {
    // console.log("pushUrl - " + url);
    var code = getuseruuid() + '-urllist'
    var ret = getItemInUmcode(code)
    // console.log("getItemInUmcode - " + ret);
    var data = ''
    if (ret === '' || ret === 'error') {
      data = url
    } else {
      var arr = ret.split(',')
      // console.log(arr);
      var lasturl = arr.pop()
      // console.log("lasturl = " + lasturl);
      arr.push(lasturl)
      if (lasturl !== url) {
        arr.push(url)
      }
      data = arr.join(',')
    }
    // console.log("setItemInUmnode - " + data);
    setItemInUmnode(code, data)
  }

  function popUrl () {
    var code = getuseruuid() + '-urllist'
    var ret = getItemInUmcode(code)
    if (ret === '' || ret === 'error') {
      return ''
    }
    var arr = ret.split(',')
    if (arr.length < 2) {
      return ''
    }
    arr.pop() // 先把当前页url去掉
    var url = arr.pop()
    setItemInUmnode(code, arr.join(','))
    return url
  }

  function getHost () {
    return location.hostname
  }
  /**
   * 删除cookie
   * @param {string} name
   */
  function eraseCookie (name) {
    createCookie(name, '', -1)
  }

  // 创建接口
  function createCore () {
    var umcore = {
      version: '1.5.18',
      /**
       * 如果与全局变量$UMCore冲突的话，可以用此方法重新指定新的名字
       * 原$UMCore变量会在调用此方法后恢复
       */
      noConflict: function () {
        global.$Umcore = _$UMCore
        return umcore
      },

      /**
       * 调用umnode的web接口。接口默认是post方式调用的。
       * @param {string} api 接口名
       * @param {Object} msg 接口需要的数据
       * @param {string} method 接口需要的发送方式。默认是post
       * @param {bool} onlydata 是否只返回data, 默认是false
       * @param {object} axios Axios的实例
       * @returns {Promise} 接口返回的数据，状态等
       */
      api: callWebapi,

      /**
       * 加载一个本地已存在的扩展模块（DApp)
       *
       * @param {string} name 加载的模块名
       * @returns {Promise} 加载的模块名
       */
      loadExt: loadExt,

      /**
       * 加载一个html或css或js. 如果type指定了类型，则以type指定的为准，如果type为空，则以路径文件的扩展名为准。
       * 异步加载。
       * 加载完后会替换selector的innerHtml
       *
       * @param {string} url 加载项的路径。如果type未指定，将会以url的扩展名为准
       * @param {string} selector 选择器，指定加载的内容显示在哪里。对css无效。
       * @param {string} type 类型，可选：css, js, html
       * @param {Object} data 如果指定data，且加载的是html的话，则按照Post的方法把data提交到url.
       * @returns {Promise} 加载的结果
       */
      load: async function (url, selector, type, data) {
        console.log('loading...', selector, url, type, data, new Date())
        let result = await loadContent(url, selector, type, data)
        console.log(result, new Date())
        return result
      },

      /**
       * 添加UI提供者
       * provider 必须包含以下接口：
       * 1. showin(selector)
       * 2. showintoolbar(selector)
       *
       * @param {string|Array} name 界面名
       * @param {Object} provider 界面提供者
       */
      addui: addProvider,

      /**
       *  初始化所有, 加载所有本地扩展
       */
      init: initall,

      /**
       * 发出广播消息
       * @param {Object} msg 消息体，一般由以下项目组成：{cmd:"", op:"", info:"", data:{}}
       *                     其中cmd是消息指令，最大的分类。其次是op操作，指定是大类下的什么动作。info用于对指令作出说明，data是指令需要的数据。
       */
      msg: pushMsg,

      /**
       * 保存或获取全局缓冲数据
       * @param {string} op 取值 'get' or 'set'
       * @param {Object|Array|string} cache 在'set'时， 以 {key: value, key2: value2} 方式指定的数据。如果key或key1已存在，将会被新的值替换
       *                             在‘get'时， 以[key, key1] 或 ’key' 方式指定数据的键，从而得到它的值。
       */
      cache: cache,

      /**
       * 添加事件侦听器. 事件是跟据消息字段`cmd`进行分组的。
       * 支持 addListener(['ke1','key2'], listener) 这种方式
       * 和   addListener('key1', listener) 这种方式
       * @param {string|Array} name 事件名
       * @param {Object} listener 侦听者
       */
      on: addListener,

      /**
       * 移除事件侦听器. 事件是跟据消息字段`cmd`进行分组的。
       * 支持 delListener(['ke1','key2'], listener) 这种方式
       * 和   delListener('key1', listener) 这种方式
       * @param {string|Array} name 事件名
       * @param {Object} listener 侦听者
       */
      del: delListener,

      /**
       * 将指定的界面展示在selector的位置处
       *
       * @param {string} selector 选择器，类似jquery的$()方法参数。
       * @param {string} providerName 界面名，通过addui注册。
       * @param {number} providerIndex 如果界面名下有多个提供者，指定其序号。
       */
      ui: showview,

      /**
       * 将指定的界面展示在selector的位置处的【工具栏】上
       *
       * @param {string} selector 选择器，类似jquery的$()方法参数。
       * @param {string} providerName 界面名，通过addui注册。
       * @param {number} providerIndex 如果界面名下有多个提供者，指定其序号。
       */
      toolbar: showviewintoolbar,

      /**
       * 设置或获得当前会话的ID
       */
      session: sessionID,

      /**
       * 得到cookie
       */
      cookie: readCookie,

      /**
       * 设置cookie
       */
      setCookie: createCookie,

      /**
       * 删除cookie
       */
      delCookie: eraseCookie,

      /**
       * 设置或得到token. 设置的token会自动带在每次请求的头部
       */
      token: jwtToken,

      /**
       *  数据直访
       */
      data: _data,
      compile: compile,
      uncompile: uncompile,
      getuseruuid: getuseruuid,
      setuseruuid: setuseruuid,
      cachebyumnode: cachebyumnode,
      isWeChatApplet: isWeChatApplet,
      pushUrl: pushUrl,
      popUrl: popUrl,
      getHost: getHost,

      /**
       * 获取设备ID
       */
      deviceId: deviceId
    }
    theCore = umcore

    if (window) {
      window.setInterval(ping, 10000) // send 'ping' to websocket server per 10 seconds
    }
    return umcore
  }

  global.$UMCore = createCore()
  return theCore
}
